package com.gupaoedu.mvcframework.servlet;

import com.gupaoedu.mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.text.html.parser.Entity;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author: WFP
 * @Time: 2018/1/27
 * @decription:
 */
public class GPDisptcherServlet extends HttpServlet {

    private Properties p = new Properties();

    private List<String> classNames = new ArrayList<>();

    private Map<String,Object> ioc = new HashMap<>();

/*    private Map<String,Method> handlerMapping = new HashMap<>();*/

    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {

        //1.加载配置文件application.properties代替xml文件
        doLoadConfig( config.getInitParameter("contextConfigLocation"));

        //2.扫描到所有相关的类,拿到基础包，递归扫描
        doScanner(p.getProperty("scanPackage"));

        //3.把扫描到的所有的类实例化，放到IOC容器中（自实现一个IOC容器，说白了就是个Map）
        doInstance();

        //4.检查注入，只要加了@GPAutowired注解的字段，不管它是私有的还是公有的，还是受保护的都要给它强制赋值
        doAutowried();
        //5.获取用户的请求，根据请求的URL去找到其对应的Method，通过反射机制去调用
        //HandlerMapping 把这样一个关系存放到HandlerMapping中去，说白了就是一个Map
        initHandlerMapping();
        //6.等待请求，把反射调用的结果通过response写出到浏览器中

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        }catch (Exception e){
            resp.getWriter().write("500 Execption , Detail: " + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        System.out.println(url);

        String contextPath = req.getContextPath();

        url = url.replace(contextPath,"").replaceAll("/+","/");
        System.out.println(url);

       /* if (!handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found");
            return;
        }
        Method method = handlerMapping.get(url);*/
        Handler handler = getHandler(req);
        if (handler==null){
            resp.getWriter().write("404 Not Found");
            return;
        }

        //获取方法的参数列表
        Class<?> [] paramTypes = handler.method.getParameterTypes();

        //保存所有需要自动赋值的参数值
        Object [] paramValues = new Object[paramTypes.length];

        //首先通过获得request的参数列表
        //获得自己定义的方法的参数列表
        Map<String ,String[]> params = req.getParameterMap();
        for (Map.Entry<String ,String []> param : params.entrySet()){
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll(",\\s",",");

            //如果找到匹配的对象，则开始填充参数值
            if (!handler.paramIndexMapping.containsKey(param.getKey())){continue;}
            int index = handler.paramIndexMapping.get(param.getKey());
            paramValues[index] = convert(paramTypes[index],value);
        }

        //设置方法中的request和response对象
        int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[reqIndex] = req;
        int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[respIndex] = resp;

        //有个坑
        //第一个参数：表示这个方法所在的实例
        //怎么办?
        //method.invoke();
        handler.method.invoke(handler.controller,paramValues);
      /*  System.out.println("成功获取到即将要调用的Method： "+method);*/
    }

    private Handler getHandler(HttpServletRequest req) throws Exception{
        if (handlerMapping.isEmpty()){return null;}

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");

        for (Handler handler : handlerMapping){
            try {
                Matcher matcher = handler.pattern.matcher(url);
                //如果没有匹配上继续下一个匹配
                if (!matcher.matches()){continue;}
                return handler;
            }catch (Exception e){
                throw e;
            }
        }
        return null;
    }

    private void doLoadConfig(String path) {
        InputStream inputStream =this.getClass().getClassLoader().getResourceAsStream(path);
        try {
            p.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void doScanner(String packageName) {
        URL url =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());
        for(File file: dir.listFiles()){
            if(file.isDirectory()){
                doScanner(packageName+"."+file.getName());
            }else {
                classNames.add(packageName+"."+file.getName().replace(".class",""));
            }
        }
    }

    private void doInstance() {
        if(classNames.isEmpty()){return;}
        try {
            for (String className : classNames){
                Class<?> clazz = Class.forName(className);

                //不是所有的类都要初始化
                if (clazz.isAnnotationPresent(GPController.class)){
                    //<bean id="" class="">
                    String beanName = lowerFirst(clazz.getSimpleName());
                    ioc.put(beanName,clazz.newInstance());
                }else if(clazz.isAnnotationPresent(GPService.class)){

                    //1.如果自己起了名字，优先使用自己的名字进行匹配注入
                    //2.默认首字母小写（发生在不是接口的情况）
                    //3.如果注入的类型是接口，我要自动找到其实现类的实例并注入

                    GPService service = clazz.getAnnotation(GPService.class);
                    String beanName = service.value();
                    if(!"".equals(beanName.trim())){//如果设了值
                        ioc.put(beanName,clazz.newInstance());
                    }else {
                        beanName = lowerFirst(clazz.getSimpleName());
                        ioc.put(beanName,clazz.newInstance());
                    }

                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces){
                        ioc.put(i.getName(),clazz.newInstance());
                    }
                }else {
                    continue;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private void doAutowried() {
        if (ioc.isEmpty()){return;}
        for (Map.Entry<String,Object> entry : ioc.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field:fields){
                if(!field.isAnnotationPresent(GPAutowired.class)){continue;}

                //如果注解加了自定义的名字
                GPAutowired autowired = field.getAnnotation(GPAutowired.class);
                String beanName = autowired.value().trim();

                //通过声明接口注入
                if ("".equals(beanName.trim())){
                    beanName = field.getType().getName();//返回该变量类型的名字
                }

                //只要加了GPAutowired注解的，我们都强行赋值。
                field.setAccessible(true);

                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void initHandlerMapping() {
        if (ioc.isEmpty()){return;}
        for (Map.Entry<String,Object> entry : ioc.entrySet()){
            //非常具有技术含量的地方

            //把所有的RequestMapping扫描出来，读取它的值，跟Method关联上，并且放到handlerMapping之中去
            Class<?> clazz = entry.getValue().getClass();

            //只跟GPController有关
            if (!clazz.isAnnotationPresent(GPController.class)){continue;}

            String baesUrl = "";
            if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
                baesUrl = requestMapping.value();
            }

            Method [] methods = clazz.getMethods();
            for (Method method: methods){
                if (!method.isAnnotationPresent(GPRequestMapping.class)){continue;}

                GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
                String mappingUrl =("/"+baesUrl+"/"+requestMapping.value()).replaceAll("/+","/");

               /* handlerMapping.put(mappingUrl,method);

                System.out.println("Mapping : "+ mappingUrl + ",Method : "+ method);*/

               //把url和Method的关系重新封装异常
                Pattern pattern = Pattern.compile(mappingUrl);
                handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                System.out.println("Mapping : "+ mappingUrl + ",Method : "+ method);
            }
        }
    }

    /**
     * Handler 记录Controller中的RequestMapping和Method对应的关系
     */
    private class  Handler{
        protected Object controller;    //保存方法对应的实例
        protected Method method;    //保存映射的方法
        protected Pattern pattern;      //记录Spring的url是否支持正则的
        protected Map<String ,Integer> paramIndexMapping;   //参数顺序

        /**
         * 构造一个Handler基本参数
         * @param pattern
         * @param controller
         * @param method
         */
        protected Handler(Pattern pattern,Object controller,Method method){
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;

            paramIndexMapping = new HashMap<String ,Integer>();
            putParamIndexMapping(method);
        }

        private void putParamIndexMapping(Method method) {
            //提取方法中加了注解的参数
            Annotation [] [] pa = method.getParameterAnnotations();
            for (int i=0;i<pa.length;i++){
                for (Annotation a : pa[i]){
                    if (a instanceof GPRequestParam){
                        String paramName = ((GPRequestParam) a).value();
                        if (!"".equals(paramName.trim())){
                            paramIndexMapping.put(paramName,i);
                        }
                    }
                }
            }

            //提取方法中的request和response参数
            Class<?> [] paramsTypes = method.getParameterTypes();
            for (int i=0;i<paramsTypes.length;i++){
                Class<?> type = paramsTypes[i];
                if (type == HttpServletRequest.class||
                        type == HttpServletResponse.class){
                    paramIndexMapping.put(type.getName(),i);
                }
            }
        }
    }

    private Object convert(Class<?> type , String value){
        if (Integer.class == type){
            return Integer.valueOf(value);
        }
        return  value;
    }

    /**
     * 首字母小写工具类
     * @param str 原始字符串
     * @return 首字母小写后的字符串
     */
    private String lowerFirst(String str){
        char [] chars = str.toCharArray();
        chars[0]+=32;
        return String.valueOf(chars);
    }






}
